package com.ymt.bpm.util.w3cdom;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

public class DataRow {
	
	/**
	 * Datarow name for datarows except first level datarow, can be any value.
	 */
	protected String localName;
	/**
	 * old localName
	 */
	protected String fromLocalName;
	
	/**
	 * Attribute of the datarow.
	 */
	protected Map<String, String> attributes;
	
	/**
	 * Sub elements of the datarow.
	 */
	protected Map<String, String> elements;
	
	/**
	 * Text content.
	 */
	protected String textContent;
	
	/**
	 * Text content.
	 */
	protected boolean useCDATAForTextContent = false;
	
	/**
	 * Children elements.
	 */
	protected List<DataRow> children;
	
	/**
	 * Create a datarow that will be added to 'REQUEST' element of the message,
	 * first level datarow must has a name of 'DATAROW'.
	 * @param localName name of the datarow element
	 */
	public DataRow(String localName){
		this.localName = localName;
	}
	
	/**
	 * Create a datarow that will be added to 'REQUEST' element of the message,
	 * first level datarow must has a name of 'DATAROW'.
	 * @param localName name of the datarow element
	 * @param attributes attribute of the datarow, use null if no
	 * @param elements elements of the datarow, use null if no
	 * @param children child datarows of the datarow, use null if no
	 */
	public DataRow(String localName, Map<String, String> attributes, LinkedHashMap<String, String> elements, List<DataRow> children){
		this.localName = localName;
		this.attributes = attributes;
		this.elements = elements;
		this.children = children;
	}
	
	/**
	 * set name for datarow.
	 * @param localName
	 */
	public void setLocalName(String localName) {
		this.localName = localName;
	}

	/**
	 * return name of current datarow.
	 * @return name of current datarow
	 */
	public String getLocalName() {
		return localName;
	}

	/**
	 * set elements of current datarow, override existing elements.
	 * @param elements
	 */
	public DataRow setElements(LinkedHashMap<String, String> elements) {
		this.elements = elements;
		return this;
	}
	
	/**
	 * return elements of current datarow.
	 * @return elements of current datarow
	 */
	public Map<String, String> getElements() {
		return elements;
	}

	/**
	 * Append a element to existing element list of current datarow.
	 * @param name use new String("xxx") instead of "xxx" if you want to keep duplicated keys
	 * @param i
	 */
	public DataRow addElement(String name, String i) {
		if (elements==null) {
			elements = Collections.synchronizedMap(new IdentityHashMap<String, String>());
		}
		elements.put(name, i);
		return this;
	}
	
	/**
	 * set attributes of current datarow, override existing attributes.
	 * @param attributes set attributes of current datarow
	 */
	public DataRow setAttributes(Map<String, String> attributes) {
		this.attributes = attributes;
		return this;
	}

	/**
	 * return attributes of current datarow.
	 * @return attributes of current datarow
	 */
	public Map<String, String> getAttributes() {
		return attributes;
	}

	/**
	 * Append a attribute to existing attribute list of current datarow.
	 * @param name
	 * @param value
	 */
	public DataRow addAttribute(String name, String value) {
		if (attributes==null) {
			attributes = Collections.synchronizedMap(new LinkedHashMap<String, String>());
		}
		attributes.put(name, value);
		return this;
	}
	
	/**
	 * Get a attribute from existing attribute list of current datarow.
	 * @param name
	 */
	public String getAttribute(String name) {
		if (attributes==null) {
			return null;
		}
		return attributes.get(name);
	}
	
	/**
	 * get text conent.
	 * @return
	 */
	public String getTextContent() {
		return textContent;
	}

	/**
	 * Datarow must not contain textContent while having child elements.
	 * @param textContent
	 */
	public void setTextContent(String textContent) {
		this.textContent = textContent;
	}
	
	/**
	 * use CDATA for text content.
	 * @return
	 */
	public boolean isUseCDATAForTextContent() {
		return useCDATAForTextContent;
	}

	/**
	 * set use CDATA for text content.
	 * @param useCDATAForTextContent
	 */
	public void setUseCDATAForTextContent(boolean useCDATAForTextContent) {
		this.useCDATAForTextContent = useCDATAForTextContent;
	}

	/**
	 * set children datarows of current datarow, override existing children.
	 * @param children children datarows of current datarow
	 */
	public DataRow setChildren(List<DataRow> children) {
		this.children = children;
		return this;
	}

	/**
	 * return children datarows of current datarow.
	 * @return children datarows of current datarow
	 */
	public List<DataRow> getChildren() {
		return children;
	}

	/**
	 * Append children to existing children list of current datarow.
	 * @param subrows
	 */
	public DataRow addChildren(DataRow... subrows) {
		if (children==null) {
			children = new ArrayList<DataRow>();
		}
		if (subrows.length>0) {
			for (DataRow subrow : subrows) {
				children.add(subrow);
			}
		}
		return this;
	}
	
	/**
	 * 删除指定Localname的子对象
	 * @MethodName: removeChildrenByLocalName
	 * @Description: 方法说明
	 * @param localName local name
	 * @param deep boolean 是否迭代所拥有子对象
	 */
	public void removeChildrenByLocalName(String localName, boolean deep) {
		if (children!=null) {
			for (int i=children.size()-1; i>=0; i--) {
				if (children.get(i).getLocalName().equalsIgnoreCase(localName)) {
					children.remove(i);
				} else if (deep) {
					children.get(i).removeChildrenByLocalName(localName, deep);
				}
			}
		}
	}
	
	public String getFromLocalName() {
		return fromLocalName;
	}

	public DataRow setFromLocalName(String fromLocalName) {
		this.fromLocalName = fromLocalName;
		return this;
	}
	
	/**
	 * 判断两个DataRow是否相等
	 * @param another
	 * @param omitNumberScale
	 * @return
	 */
	public boolean equals(DataRow another, boolean omitNumberScale) {
		if (another==null) {
			return false;
		}
		//compare localName
		if (!this.getLocalName().equals(another.getLocalName())) {
			return false;
		}
		//compare attributes
		if (!isEqualMap(this.attributes, another.getAttributes(), omitNumberScale)) {
			return false;
		}
		//compare elements
		if (!isEqualMap(this.elements, another.getAttributes(), false)) {
			return false;
		}
		//compare text content
		if (!StringUtils.equals(this.textContent, another.getTextContent())) {
			return false;
		}
		//compare children
		if (this.children!=null && another.getChildren()==null) {
			return false;
		}
		if (this.children==null && another.getChildren()!=null) {
			return false;
		}
		if (this.children!=null && another.getChildren()!=null) {
			if (this.children.size()!=another.getChildren().size()) {
				return false;
			}
			for (int i=0; i<this.children.size(); i++) {
				if (!this.children.get(i).equals(another.getChildren().get(i), omitNumberScale)) {
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * 判断两个map是否相等
	 * @param map1
	 * @param map2
	 * @param omitNumberScale 是否将值作为数字比较，并且忽略数字小数点进行比较
	 * @return
	 */
	private boolean isEqualMap(Map<String, String> map1, Map<String, String> map2, boolean omitNumberScale) {
		if (map1==null && map2!=null) {
			return false;
		}
		if (map1!=null && map2==null) {
			return false;
		}
		if (map1!=null && map2!=null) {
			if (map1.size()!=map2.size()) {
				return false;
			}
			for (Map.Entry<String, String> attr : map1.entrySet()) {
				if (!map2.containsKey(attr.getKey())) {
					return false;
				}
				if (attr.getValue()==null && map2.get(attr.getKey())!=null) {
					return false;
				}
				if (attr.getValue()!=null) {
					if (map2.get(attr.getKey())==null) {
						return false;
					}
					if (omitNumberScale && StringUtils.isNumeric(attr.getValue()) && StringUtils.isNumeric(map2.get(attr.getKey()))) {
						BigDecimal d1 = new BigDecimal(attr.getValue());
						BigDecimal d2 = new BigDecimal(attr.getValue());
						d1 = d1.setScale(0);
						d2 = d2.setScale(0);
						if (d1.compareTo(d2)!=0) {
							return false;
						}
					} else {
						if (!attr.getValue().equals(map2.get(attr.getKey()))) {
							return false;
						}
					}
				}
			}
		}
		return true;
	}

	public static void print(DataRow datarow) {
		System.out.println("Printing DataRow Start: ============================");
		System.out.println("localName      =====" + datarow.getLocalName());
		if (datarow.getTextContent()!=null) {
			System.out.println("  text content: " + datarow.getTextContent());
		}
		if (datarow.getAttributes() != null && datarow.getAttributes().size()>0) {
			for (String key : datarow.getAttributes().keySet()) {
				System.out.println("  attribute: " + key + "=" + datarow.getAttributes().get(key));
			}
		}
		if (datarow.getElements() != null && datarow.getElements().size()>0) {
			for (String key : datarow.getElements().keySet()) {
				System.out.println("  elements: " + key + "=" + datarow.getElements().get(key));
			}
		}
		if (datarow.getChildren() != null && datarow.getChildren().size()>0) {
			for (int i=0; i<datarow.getChildren().size(); i++) {
				print(datarow.getChildren().get(i));
			}
		}
		System.out.println("Printing DataRow End: ============================");
		System.out.println();
	}
	
	public DataRow filterAttr(String[] names, AttrFilter filter, boolean deep) {
		if (names==null || names.length==0) {
			return this;
		}
		for (String name : names) {
			if (getAttributes() != null && getAttributes().containsKey(name)) {
				getAttributes().put(name, filter.translate(getAttributes().get(name)));
			}
			if (getChildren() != null && deep) {
				DataRow.filterAttr(getChildren(), names, filter, true);
			}
		}
		return this;
	}
	
	public static void filterAttr(List<DataRow> datarows, String[] names, AttrFilter filter, boolean deep) {
		if (datarows==null || names==null || names.length==0) {
			return;
		}
		for (String name : names) {
			for (DataRow datarow:datarows) {
				if (datarow.getAttributes() != null && datarow.getAttributes().containsKey(name)) {
					datarow.getAttributes().put(name, filter.translate(datarow.getAttributes().get(name)));
				}
				if (datarow.getChildren() != null && deep) {
					DataRow.filterAttr(datarow.getChildren(), names, filter, true);
				}
			}
		}
	}
	
	public DataRow removeAttr(String name, boolean deep) {
		if (getAttributes() != null) {
			this.getAttributes().remove(name);
		}
		if (getChildren() != null && deep) {
			DataRow.removeAttr(getChildren(), name, true);
		}
		return this;
	}
	
	public static void removeAttr(List<DataRow> datarows, String name, boolean deep) {
		if (datarows==null) {
			return;
		}
		for (DataRow datarow:datarows) {
			if (datarow.getAttributes() != null) {
				datarow.getAttributes().remove(name);
			}
			if (datarow.getChildren() != null && deep) {
				DataRow.removeAttr(datarow.getChildren(), name, true);
			}
		}
	}
}
